<?php
/**
 * +--------------------------------------------------------------
 * | eLuoPHP
 * +--------------------------------------------------------------
 * | Copyright (c) 2013-2019 http://qxwoo.cn All rights reserved.
 * +--------------------------------------------------------------
 * | y139w <y139w@163.com>
 * +--------------------------------------------------------------
 * | 2019/07/21 23:45:11
 * +--------------------------------------------------------------
 * | Upload.php +--------------------------------------------------------------
 */


namespace luo;


class Upload
{

    /**
     * @var string
     */
    private $phpPath = '';

    /**
     * @var string
     */
    private $phpUrl = '';

    /**
     * 文件保存目录路径
     * @var string
     */
    private $savePath = '';

    /**
     * 文件保存目录URL
     * @var string
     */
    private $saveUrl = '';

    /**
     * 是否使用原始文件名称
     * @var bool
     */
    private $isName = false;

    /**
     * 定义上传的文件扩展名
     * @var array
     */
    private $extArray = [
        'image' => ['gif', 'jpg', 'jpeg', 'png', 'bmp'],
        'flash' => ['swf', 'flv'],
        'media' => ['swf', 'flv', 'mp3', 'wav', 'wma', 'wmv', 'mid', 'avi', 'mpg', 'asf', 'rm', 'rmvb'],
        'file'  => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'htm', 'html', 'txt', 'zip', 'rar', 'gz', 'bz2'],
    ];

    /**
     * 允许上传的文件扩展名
     * @var array
     */
    private $allowType = ['jpg', 'jpeg', 'png', 'bmp'];

    /**
     * 上传文件扩展名对应文件类型
     * @var array
     */
    private $typeArray = [
        'gif'  => 'image/gif',
        'jpg'  => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png'  => 'image/png',
        'bmp'  => 'image/bmp',
        'swf'  => 'application/x-shockwave-flash',
        'flv'  => 'video/x-flv',
        'mp3'  => 'audio/mp3|audio/mpeg',
        'wav'  => 'audio/wav',
        'wma'  => 'audio/x-ms-wma',
        'wmv'  => 'video/x-ms-wmv',
        'mid'  => 'audio/mid',
        'avi'  => 'video/avi',
        'mpg'  => 'video/mpg',
        'asf'  => 'video/x-ms-asf',
        'rm'   => 'application/vnd.rn-realmedia',
        'rmvb' => 'application/vnd.rn-realmedia-vbr',
        'doc'  => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document|application/zip',
        'xls'  => 'application/vnd.ms-excel|application/x-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'ppt'  => 'application/vnd.ms-powerpoint',
        'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'htm'  => 'text/html',
        'html' => 'text/html',
        'txt'  => 'text/plain',
        'zip'  => 'application/zip',
        'rar'  => 'application/x-rar',
        'gz'   => 'application/x-gzip',
        'bz2'  => 'application/x-bzip2'
    ];

    /**
     * 最大文件大小（MB）
     * @var int
     */
    private $maxSize = 2;

    /**
     * @var string
     */
    private $dirName = '';

    /**
     * @var bool
     */
    private $isCheckType = false;

    /**
     * @var null
     */
    private $mime_content_type = null;

    /**
     * @var string
     */
    private $attachment_directory = '';

    /**
     * @var array
     */
    private $_files = [];

    /**
     * Upload constructor.
     * @param string $attached
     */
    public function __construct($attached = 'uploads/')
    {

        // 网站根目录
        $document_root = $_SERVER['DOCUMENT_ROOT'];
        // 入口文件目录
        $script_filename = dirname($_SERVER['SCRIPT_FILENAME']);
        $this->phpPath = $script_filename . '/';
        // 网站相对路径
        $root = str_replace($document_root, '', $script_filename);
        $this->phpUrl = ($root === '/') ? '/' : $root . '/';
        $this->savePath = realpath($this->phpPath . $attached) . '/';
        $this->saveUrl = $this->phpUrl . $attached;
        $this->attachment_directory = $attached;

    }

    /**
     * 设置允许上传文件
     * @param array $suffix
     * @return $this
     */
    public function suffix(array $suffix = []){

        $this->allowType += $suffix;
        return $this;

    }

    /**
     * 单文件上传
     * @param string $input_name_key
     * @param string $dir
     * @return array
     */
    public function up($input_name_key = '', $dir = ''){

        $this->_files = $_FILES;
        $file = $this->_files[$input_name_key] ?? false;
        if(empty($input_name_key) || $file === false){
            $file = current($this->_files);
            $file['input_name_key'] = key($this->_files);
        }
        return $this->save($file, $dir);

    }

    /**
     * @param $file
     * @param $dir
     * @return array
     */
    public function save($file, $dir){

        $result = ['status'=>true, 'error'=>'成功', 'data'=>[]];
        $name = $file['name'];
        // 上传错误
        $error = $this->error($file['error']);
        if($error['status'] === false){
            $result = $error;
        }
        // 是否选择文件
        if(!$name){
            $result = ['status'=>false, 'error'=>'请选择文件'];
        }
        // 相关信息检查
        $checking = $this->checking($file, $dir);
        if($checking['status'] === false){
            $result = $checking;
        }
        // 文件后缀名
        $fileExt = $this->fileExt($file['name']);
        // 检查后缀名
        if(!$this->checkExt($fileExt)){
            $result = ['status'=>false, 'error'=>'上传文件扩展名是不允许的扩展名，\n只允许' . implode(",", $this->allowType) . '格式'];
            if($this->isCheckType){
                if(!$this->checkType($fileExt, $file)){
                    $result = ['status'=>false, 'error'=>'上传文件类型错误'];
                }
            }
        }
        if($result['status'] === true){
            // 移动文件到文件夹
            if(!$moveFile = $this->moveFile($file)){
                $result = ['status'=>false, 'error'=>'上传失败'];
            }else{
                $result['data'] = $moveFile;
            }
        }
        return $result;

    }

    /**
     * 相关信息检查
     * @param $file
     * @param $dir
     * @return array
     */
    private function checking($file, $dir){

        $checking = ['status'=>true, 'error'=>'ok'];
        // 检查目录
        if(!$this->isDir($this->savePath)){
            $checking = ['status'=>false, 'error'=>'上传目录不存在'];
        }
        // 检查目录写权限
        if(!$this->isWritable($this->savePath)){
            $checking = ['status'=>false, 'error'=>'上传目录没有写权限'];
        }
        // 检查是否已上传
        if(!$this->isUploadedFile($file['tmp_name'])){
            $checking = ['status'=>false, 'error'=>'文件未上传'];
        }
        // 检查文件大小
        if(!$this->checkSize($file['size'])){
            $checking = ['status'=>false, 'error'=>'上传文件大小超过限制'];
        }
        // 检查目录名
        if(!$this->dirName($dir)){
            $checking = ['status'=>false, 'error'=>'目录名不正确'];
        }
        return $checking;

    }

    /**
     * 移动文件
     * @param $file
     * @return bool|string
     */
    private function moveFile($file){

        $moveFile = true;
        // 创建文件夹
        $this->createDir();
        // 生成新文件名
        $newFileName = $this->fileName($file['name']);
        // 移动文件
        $filePath = $this->savePath . $newFileName;
        if (move_uploaded_file($file['tmp_name'], $filePath) === false) {
            $moveFile = false;
        }
        chmod($filePath, 0644);
        $fileUrl = $this->saveUrl . $newFileName;
        if($moveFile){
            $file['save_url'] = $this->saveUrl;
            $file['filename'] = $newFileName;
            $file['file_url'] = $fileUrl;
            $mime_content_type = $file['type'];
            if(!is_null($this->mime_content_type)){
                $mime_content_type = $this->mime_content_type;
            }
            $file['mime_content_type'] = $mime_content_type;
            $file['attachment_directory'] = $this->attachment_directory;
            $file['save_path'] = $this->attachment_directory;
            $file['save_name'] = $newFileName;
        }
        return $moveFile ? $file : false;

    }

    /**
     * 验证文件类型
     * @param $ext
     * @param $file
     * @return bool
     */
    private function checkType($ext, $file){

        $checkType = false;
        $this->mime_content_type = $file['type'];
        // \finfo类是否存在，如果存在，就使用\finfo获取文件扩展名。
        if(class_exists('\finfo')){
            $finfo = new \finfo(FILEINFO_MIME);
            $this->mime_content_type = $finfo->file($file['tmp_name']);
        }
        $suffixCorrespondingType = $this->typeArray[$ext] ?? false;
        if($suffixCorrespondingType){
            $suffixCorrespondingTypeArray = [$suffixCorrespondingType];
            if(strpos($suffixCorrespondingType, '|')){
                $suffixCorrespondingTypeArray = explode('|', $suffixCorrespondingType);
            }
            foreach($suffixCorrespondingTypeArray as $val){
                if($val == $this->mime_content_type){
                    $checkType = true;
                }
            }
        }
        return $checkType;

    }

    /**
     * 处理文件名
     * @param $fileName
     * @param string $name
     * @return string
     */
    private function fileName($fileName, $name = ''){

        // 扩展名
        $fileExt = $this->fileExt($fileName);
        if($fileName === false){
            if(is_callable($name)){
                $newFileName = $name($fileName, $fileExt);
            }else{
                $newFileName = $name;
            }
        }else{
            if(!$this->isName){
                // 新文件名
                $newFileName = implode('', [date("YmdHis"), '_', rand(10000, 99999), '.', $fileExt]);
            }else{
                $newFileName = $fileName;
            }
        }
        return $newFileName;

    }

    /**
     * 创建文件夹
     * @return $this
     */
    private function createDir(){

        //创建文件夹
        if ($this->dirName !== '') {
            $this->savePath .= $this->dirName . '/';
            $this->saveUrl  .= $this->dirName . '/';
            $this->attachment_directory .= $this->dirName . '/';
            if (!file_exists($this->savePath)) {
                mkdir($this->savePath);
            }
        }
        $ymd = date("Ymd");
        $this->savePath .= $ymd . "/";
        $this->saveUrl  .= $ymd . "/";
        $this->attachment_directory .= $ymd . "/";
        if (!file_exists($this->savePath)) {
            mkdir($this->savePath);
        }
        return $this;

    }

    /**
     * 检查扩展名
     * @param $fileExt
     * @return bool
     */
    private function checkExt($fileExt){

        $checkExt = true;
        //检查扩展名
        if (in_array($fileExt, $this->allowType) === false) {
            $checkExt = false;
        }
        return $checkExt;

    }

    /**
     * 获得文件扩展名
     * @param $fileName
     * @return mixed|string
     */
    private function fileExt($fileName){

        //获得文件扩展名
        $tempArr = explode(".", $fileName);
        $fileExt = array_pop($tempArr);
        $fileExt = trim($fileExt);
        $fileExt = strtolower($fileExt);
        return $fileExt;

    }

    /**
     * 检查目录名
     * @param $dir
     * @return bool
     */
    private function dirName($dir){

        $dirName = true;
        //检查目录名
        $this->dirName = empty($dir) ? 'image' : trim($dir);
        if (empty($this->extArray[$this->dirName])) {
            $dirName = false;
        }
        return $dirName;

    }

    /**
     * 检查文件大小
     * @param $fileSize
     * @return bool
     */
    private function checkSize($fileSize){

        $checkSize = true;
        // 最大文件大小转换（MB -> B）
        $maxSize = $this->maxSize * 1024 * 1024;
        //检查文件大小
        if ($fileSize > $maxSize) {
            $checkSize = false;
        }
        return $checkSize;

    }

    /**
     * 检查是否已上传
     * @param $tmpName
     * @return bool
     */
    private function isUploadedFile($tmpName){

        $isUploadedFile = true;
        //检查是否POST上传
        if (is_uploaded_file($tmpName) === false) {
            $isUploadedFile = false;
        }
        return $isUploadedFile;

    }

    /**
     * 检查目录写权限
     * @param $savePath
     * @return bool
     */
    private function isWritable($savePath){

        $isWritable = true;
        //检查目录写权限
        if (is_writable($savePath) === false) {
            $isWritable = false;
        }
        return $isWritable;

    }

    /**
     * 检查目录
     * @param $savePath
     * @return bool
     */
    private function isDir($savePath){

        $isDir = true;
        //检查目录
        if (is_dir($savePath) === false) {
            $isDir = false;
        }
        return $isDir;

    }

    /**
     * 上传错误
     * @param $errorCode
     * @return array
     */
    private function error($errorCode){

        $error = true;
        //PHP上传失败
        if (!empty($errorCode)) {
            switch($errorCode){
                case '1':
                    $error = '超过php.ini允许的大小。';
                    break;
                case '2':
                    $error = '超过表单允许的大小。';
                    break;
                case '3':
                    $error = '图片只有部分被上传。';
                    break;
                case '4':
                    $error = '请选择图片。';
                    break;
                case '6':
                    $error = '找不到临时目录。';
                    break;
                case '7':
                    $error = '写文件到硬盘出错。';
                    break;
                case '8':
                    $error = 'File upload stopped by extension。';
                    break;
                case '999':
                default:
                    $error = '未知错误。';
            }
        }
        return ['status'=>($error == true) ? true : false, 'error'=> $error];

    }

}